package com.morpheus.redis.jedis.config;

import java.time.Duration;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisClusterConfiguration;
import org.springframework.data.redis.connection.RedisPassword;
import org.springframework.data.redis.connection.jedis.JedisClusterConnection;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;

import com.morpheus.redis.jedis.annotation.ConditionalOnJedisClusterConfig;

import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.JedisPoolConfig;

@Configuration
@EnableConfigurationProperties({ RedisProperties.class, JedisProperties.class })
@ConditionalOnJedisClusterConfig
public class JedisClusterAutoConfiguration {
	private static final Logger LOGGER = LoggerFactory.getLogger(JedisClusterAutoConfiguration.class);
	private static final String DEFAULT_JMX_NAME_BASE = "jedis";
	private static final String DEFAULT_JMX_NAME_PREFIX = "jmx";
	private static final int DEFAULT_TIMEOUT = 30000;
	private static final int DEFAULT_MAX_ATTEMPTS = 1;

	@Bean
	public JedisPoolConfig jedisPoolConfig(RedisProperties redisProperties, JedisProperties jedisProperties) {
		JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
		jedisPoolConfig.setJmxEnabled(true);
		jedisPoolConfig.setJmxNameBase(DEFAULT_JMX_NAME_BASE);
		jedisPoolConfig.setJmxNamePrefix(DEFAULT_JMX_NAME_PREFIX);
		jedisPoolConfig.setMaxIdle(redisProperties.getJedis().getPool().getMaxIdle());
		jedisPoolConfig.setMinIdle(redisProperties.getJedis().getPool().getMinIdle());
		jedisPoolConfig.setMaxTotal(redisProperties.getJedis().getPool().getMaxActive());
		jedisPoolConfig.setMaxWaitMillis(redisProperties.getJedis().getPool().getMaxWait().toMillis());
		jedisPoolConfig.setTimeBetweenEvictionRunsMillis(
				redisProperties.getJedis().getPool().getTimeBetweenEvictionRuns().toMillis());
		jedisPoolConfig.setTestOnBorrow(jedisProperties.getTest().isOnBorrow());
		jedisPoolConfig.setTestOnCreate(jedisProperties.getTest().isOnCreate());
		jedisPoolConfig.setTestOnReturn(jedisProperties.getTest().isOnReturn());
		jedisPoolConfig.setTestWhileIdle(jedisProperties.getTest().isWhileIdle());
		LOGGER.debug("JedisClusterAutoConfiguration.jedisPoolConfig() jedisPoolConfig={}", jedisPoolConfig);
		return jedisPoolConfig;
	}

	@Bean
	public RedisClusterConfiguration redisClusterConfiguration(RedisProperties redisProperties) {
		RedisClusterConfiguration redisClusterConfiguration = new RedisClusterConfiguration(
				redisProperties.getCluster().getNodes());
		redisClusterConfiguration.setPassword(RedisPassword.of(redisProperties.getPassword()));
		redisClusterConfiguration.setMaxRedirects(redisProperties.getCluster().getMaxRedirects());
		LOGGER.debug("JedisClusterAutoConfiguration.redisClusterConfiguration() redisClusterConfiguration={}",
				redisClusterConfiguration);
		return redisClusterConfiguration;
	}

	@Bean
	public JedisCluster jedisCluster(JedisPoolConfig poolConfig, RedisProperties redisProperties,
			JedisProperties jedisProperties) {
		List<String> nodeList = redisProperties.getCluster().getNodes();
		Set<HostAndPort> jedisClusterNode = this.getClusterHostAndPort(nodeList);
		int timeout = Long.valueOf(
				Optional.ofNullable(redisProperties.getTimeout()).orElse(Duration.ofMillis(DEFAULT_TIMEOUT)).toMillis())
				.intValue();
		int maxAttempts = Optional.ofNullable(jedisProperties.getMaxAttempts()).orElse(1);
		JedisCluster jedisCluster = new JedisCluster(jedisClusterNode, timeout, maxAttempts, poolConfig);
		LOGGER.debug("JedisClusterAutoConfiguration.jedisCluster() jedisCluster={}", jedisCluster);
		return jedisCluster;
	}

	@Bean
	public JedisClusterConnection jedisClusterConnection(JedisCluster cluster) {
		JedisClusterConnection jedisClusterConnection = new JedisClusterConnection(cluster);
		LOGGER.debug("JedisClusterAutoConfiguration.jedisClusterConnection() jedisClusterConnection={}",
				jedisClusterConnection);
		return jedisClusterConnection;
	}

	@Bean(destroyMethod = "destroy")
	public JedisConnectionFactory redisConnectionFactory(RedisClusterConfiguration clusterConfig,
			JedisPoolConfig poolConfig) {
		JedisConnectionFactory redisConnectionFactory = new JedisConnectionFactory(clusterConfig, poolConfig);
		LOGGER.debug("JedisClusterAutoConfiguration.redisConnectionFactory() redisConnectionFactory={}",
				redisConnectionFactory);
		return redisConnectionFactory;
	}

	@Bean
	public StringRedisTemplate stringRedisTemplate(JedisConnectionFactory connectionFactory) {
		StringRedisTemplate stringRedisTemplate = new StringRedisTemplate(connectionFactory);
		return stringRedisTemplate;
	}

	@Bean
	public RedisTemplate<Object, Object> redisTemplate(JedisConnectionFactory connectionFactory) {
		RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<Object, Object>();
		redisTemplate.setConnectionFactory(connectionFactory);
		return redisTemplate;
	}

	public Set<HostAndPort> getClusterHostAndPort(List<String> nodeList) {
		nodeList = Optional.ofNullable(nodeList).orElse(new ArrayList<String>());
		Set<HostAndPort> resList = new HashSet<HostAndPort>();
		for (String node : nodeList) {
			HostAndPort hostAndPort = HostAndPort.parseString(node);
			resList.add(hostAndPort);
		}
		return resList;
	}
}
